package nodeRegister

import (
	"sync"
	"time"

	"github.com/golang/glog"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	authapi "eccgateway/pkg/api/auth"
	"eccgateway/pkg/api/nodeRegister"
	"eccgateway/pkg/config"
	"eccgateway/pkg/util"
)

type RegisterService struct {
	queue      *util.LinkedQueue
	kubeClient *kubernetes.Clientset
}

var svc RegisterService
var once sync.Once

type registerAndInfo struct {
	req       nodeRegister.Register
	info      *authapi.RespToken
	timestamp time.Time
}

const (
	EditUserKey       = "editUser"
	ClusterLabelKey   = "endedgecloud.zjlab.io/cluster"
	CloudRoleLabelKey = "node-role.kubernetes.io/cloud"
	GroupIDKey     = "endedgecloud.zjlab.io/groupid"

	AddLabelTimeoutPeriod = 5 * time.Minute
)

func init() {
	once.Do(func() {
		var err error
		var cfg *rest.Config
		masterURL := config.GetConfig().String(config.MasterURLKey)
		kubeconfig := config.GetConfig().String(config.KubeConfigKey)
		if masterURL == "" && kubeconfig == "" {
			cfg, err = rest.InClusterConfig()
			if err != nil {
				glog.Fatalf("Error building kubeconfig in cluster: %s", err.Error())
			}
		} else {
			cfg, err = clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
			if err != nil {
				glog.Fatalf("Error building kubeconfig by kube config file: %s", err.Error())
			}
		}
		kubeClient, err := kubernetes.NewForConfig(cfg)
		if err != nil {
			glog.Fatalf("Error building kubernetes client: %s", err.Error())
		}

		svc = RegisterService{
			queue:      &util.LinkedQueue{},
			kubeClient: kubeClient,
		}
		go svc.Worker()
	})
}

func GetRegisterService() *RegisterService {
	return &svc
}

func (s *RegisterService) AddRegisterNodeInCache(req nodeRegister.Register, info *authapi.RespToken) error {
	s.queue.Add(registerAndInfo{
		req:       req,
		info:      info,
		timestamp: time.Now(),
	})
	return nil
}

func (s *RegisterService) Worker() {
	for s.processNextWorkItem() {
		time.Sleep(100 * time.Millisecond)
	}
}

func (s *RegisterService) processNextWorkItem() bool {
	detail := s.queue.Get()
	if detail == nil {
		return true
	}
	detailWork, ok := detail.(registerAndInfo)
	if !ok {
		glog.Errorf("failed to get register node and user info struct from cache queue")
		return false
	}

	existFunc := func(detailWork registerAndInfo) {
		t := time.Now()
		if t.After(detailWork.timestamp.Add(AddLabelTimeoutPeriod)) {
			glog.Errorf("add labels in node[%v] timeout, user: %v, cluster: %v", detailWork.req.Node, detailWork.info.Name, detailWork.req.Cluster)
			return
		}
		s.queue.Add(registerAndInfo{
			req:       detailWork.req,
			info:      detailWork.info,
			timestamp: t,
		})
	}

	node, err := s.kubeClient.CoreV1().Nodes().Get(detailWork.req.Node, metav1.GetOptions{})
	if errors.IsNotFound(err) {
		existFunc(detailWork)
		return true
	}

	isNeedUpdate := false

	if node.Annotations == nil {
		node.Annotations = make(map[string]string)
	}
	if _, ok := node.Annotations[EditUserKey]; !ok {
		isNeedUpdate = true
	}
	node.Annotations[EditUserKey] = detailWork.info.Name

	if node.Labels == nil {
		node.Labels = make(map[string]string)
	}
	if _, ok := node.Labels[ClusterLabelKey]; !ok {
		isNeedUpdate = true
	}
	node.Labels[ClusterLabelKey] = detailWork.req.Cluster

	if detailWork.req.Type == nodeRegister.CloudType {
		if _, ok := node.Labels[CloudRoleLabelKey]; !ok {
			isNeedUpdate = true
		}
		node.Labels[CloudRoleLabelKey] = ""
	}

	cluster, err := util.GetCluster(detailWork.req.Cluster)
	if err == util.NotFoundError {
		glog.Errorf("get cluster[%v] from kubernetes failed, err: %v", detailWork.req.Cluster, err)
		existFunc(detailWork)
		return true
	}
	if err != nil {
		glog.Errorf("get cluster[%v] from kubernetes failed, err: %v", detailWork.req.Cluster, err)
		existFunc(detailWork)
		return true
	}
	if _, ok := node.Labels[GroupIDKey]; !ok {
		isNeedUpdate = true
	}
	node.Labels[GroupIDKey] = cluster.Labels[GroupIDKey]

	if !isNeedUpdate {
		// node is updated finished
		glog.Infof("node[%v] is update finished", detailWork.req.Node)
		return true
	}

	_, err = s.kubeClient.CoreV1().Nodes().Update(node)
	if err != nil {
		glog.Errorf("failed to add labels in node[%v], user: %v, cluster: %v, err: %v", detailWork.req.Node, detailWork.info.Name, detailWork.req.Cluster, err)
		existFunc(detailWork)
		return true
	}
	return true
}
